# js_min.rb the library 2008-03-05
# Author: Eden Li
#
# Isolation of Uladzislau Latynski's jsmin.rb into a ruby library.
# Permission is hereby granted to use the Ruby /library/ version 
# under the same conditions as the jsmin.rb on which it is based.

# jsmin.rb 2007-07-20
# Author: Uladzislau Latynski
# This work is a translation from C to Ruby of jsmin.c published by
# Douglas Crockford.  Permission is hereby granted to use the Ruby
# version under the same conditions as the jsmin.c on which it is
# based.

# /* jsmin.c
#    2003-04-21
#
# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# The Software shall be used for Good, not Evil.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

require 'stringio'
class JsMin
  EOF = -1 unless defined? EOF

  def self.mime_type
    "text/javascript"
  end

  def initialize(js)
    @in = StringIO.new(js.gsub(/\r\n/, "\n"))
    @out = StringIO.new
    @a = ""
    @b = ""
  end

  # isAlphanum -- return true if the character is a letter, digit, underscore,
  # dollar sign, or non-ASCII character
  def isAlphanum(c)
    return false if !c || c == EOF
    return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
    (c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
    c == '\\' || c[0] > 126)
  end

  # get -- return the next character from stdin. Watch out for lookahead. If
  # the character is a control character, translate it to a space or linefeed.
  def get()
    c = @in.getc
    return EOF if(!c)
    c = c.chr
    return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
    return "\n" if (c == "\r")
    return " "
  end

  # Get the next character without getting it.
  def peek()
    lookaheadChar = @in.getc
    @in.ungetc(lookaheadChar)
    return lookaheadChar.chr
  end

  # mynext -- get the next character, excluding comments.
  # peek() is used to see if a '/' is followed by a '/' or '*'.
  def mynext()
    c = get
    if (c == "/")
      if(peek == "/")
        while(true)
          c = get
          if (c <= "\n")
            return c
          end
        end
      end
      if(peek == "*")
        get
        while(true)
          case get
          when "*"
            if (peek == "/")
              get
              return " "
            end
          when EOF
            raise "Unterminated comment"
          end
        end
      end
    end
    return c
  end


  # action -- do something! What you do is determined by the argument: 1
  # Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
  # (Delete A). 3 Get the next B. (Delete B). action treats a string as a
  # single character. Wow! action recognizes a regular expression if it is
  # preceded by ( or , or =.
  def action(a)
    if(a==1)
      @out.write @a
    end
    if(a==1 || a==2)
      @a = @b
      if (@a == "\'" || @a == "\"")
        while (true)
          @out.write @a
          @a = get
          break if (@a == @b)

          if (@a <= "\n")
            @out.rewind
            raise "Unterminated string literal current stream=#{@out.read}"
          end
          if (@a == "\\")
            @out.write @a
            @a = get
          end
        end
      end
    end
    if(a==1 || a==2 || a==3)
      @b = mynext
      if (@b == "/" && (@a == "(" || @a == "," || @a == "=" ||
        @a == ":" || @a == "[" || @a == "!" ||
        @a == "&" || @a == "|" || @a == "?" ||
        @a == "{" || @a == "}" || @a == ";" ||
        @a == "\n"))
        @out.write @a
        @out.write @b
        while (true)
          @a = get
          if (@a == "/")
            break
          elsif (@a == "\\")
            @out.write @a
            @a = get
          elsif (@a <= "\n")
            raise "Unterminated RegExp Literal"
          end
          @out.write @a
        end
        @b = mynext
      end
    end
  end

  def self.minify(js)
    new(js).minify
  end

  # jsmin -- Copy the input to the output, deleting the characters which are
  # insignificant to JavaScript. Comments will be removed. Tabs will be
  # replaced with spaces. Carriage returns will be replaced with linefeeds.
  # Most spaces and linefeeds will be removed.
  def minify
    @a = "\n"
    action(3)
    while (@a != EOF)
      case @a
      when " "
        if (isAlphanum(@b))
          action(1)
        else
          action(2)
        end
      when "\n"
        case (@b)
        when "{","[","(","+","-"
          action(1)
        when " "
          action(3)
        else
          if (isAlphanum(@b))
            action(1)
          else
            action(2)
          end
        end
      else
        case (@b)
        when " "
          if (isAlphanum(@a))
            action(1)
          else
            action(3)
          end
        when "\n"
          case (@a)
          when "}","]",")","+","-","\"","\\", "'", '"'
            action(1)
          else
            if (isAlphanum(@a))
              action(1)
            else
              action(3)
            end
          end
        else
          action(1)
        end
      end
    end

    @out.rewind
    @out.read
  end
end
